<!DOCTYPE html>


<html lang="zh-CN">


<head>
  <meta charset="utf-8" />
    
  <meta name="description" content="迎着朝阳的博客" />
  
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
  <title>
    Elastic-Job 架构与整体介绍 |  迎着朝阳
  </title>
  <meta name="generator" content="hexo-theme-ayer">
  
  <link rel="shortcut icon" href="https://dxysun.com/static/yan.png" />
  
  
<link rel="stylesheet" href="/dist/main.css">

  
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css">

  
<link rel="stylesheet" href="/css/custom.css">

  
  
<script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>

  
  

  
<script>
var _hmt = _hmt || [];
(function() {
	var hm = document.createElement("script");
	hm.src = "https://hm.baidu.com/hm.js?aa994a8d65700b8835787dd39d079d7e";
	var s = document.getElementsByTagName("script")[0]; 
	s.parentNode.insertBefore(hm, s);
})();
</script>


</head>

</html>

<body>
  <div id="app">
    
      
    <main class="content on">
      <section class="outer">
  <article
  id="post-elasticJob"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  Elastic-Job 架构与整体介绍
</h1>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/12/12/elasticJob/" class="article-date">
  <time datetime="2020-12-12T15:57:58.000Z" itemprop="datePublished">2020-12-12</time>
</a>   
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> 字数统计:</span>
            <span class="post-count">5.2k</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> 阅读时长≈</span>
            <span class="post-count">18 分钟</span>
        </span>
    </span>
</div>
 
    </div>
      
    <div class="tocbot"></div>




  
    <div class="article-entry" itemprop="articleBody">
       
  <p>Elastic-Job 是 ddframe 中的 dd-job 作业模块分离出来的作业框架，基于 Quartz 和 Curator 开发，在 2015 年开源。</p>
<a id="more"></a>



<h2 id="基本介绍"><a href="#基本介绍" class="headerlink" title="基本介绍"></a>基本介绍</h2><p>Elastic-Job提供了一种轻量级，无中心化解决方案。</p>
<p>没有统一的调度中心。集群的每个节点都是对等的， 节点之间通过注册中心进行分布式协调。E-Job 存在主节点的概念，但是主节点没有调度 的功能，而是用于处理一些集中式任务，如分片，清理运行时信息等。</p>
<p>Elastic-Job 最开始只有一个 elastic-job-core 的项目，在 2.X 版本以后主要分为 Elastic-Job-Lite 和 Elastic-Job-Cloud 两个子项目。</p>
<p>其中，Elastic-Job-Lite 定位为轻量 级无中心化解决方案，使用 jar 包的形式提供分布式任务的协调服务。而 Elastic-Job-Cloud 使用 Mesos + Docker 的解决方案，额外提供资源治理、应用分发以及进程隔离等服务(跟 Lite 的区别只是部署方式不同，他们使用相同的 API，只要开发一次)。</p>
<table>
<thead>
<tr>
<th></th>
<th>Elastic-Job-Lite</th>
<th>Elastic-Job-Cloud</th>
</tr>
</thead>
<tbody><tr>
<td>无中心化</td>
<td>是</td>
<td>否</td>
</tr>
<tr>
<td>资源分配</td>
<td>不支持</td>
<td>支持</td>
</tr>
<tr>
<td>作业模式</td>
<td>常驻</td>
<td>常驻 + 瞬时</td>
</tr>
<tr>
<td>部署依赖</td>
<td>ZooKeeper</td>
<td>ZooKeeper + Mesos</td>
</tr>
</tbody></table>
<h2 id="功能列表"><a href="#功能列表" class="headerlink" title="功能列表"></a>功能列表</h2><ul>
<li><p>弹性调度</p>
</li>
<li><ul>
<li>支持任务在分布式场景下的分片和高可用</li>
<li>能够水平扩展任务的吞吐量和执行效率</li>
<li>任务处理能力随资源配备弹性伸缩</li>
</ul>
</li>
<li><p>资源分配</p>
</li>
<li><ul>
<li>在适合的时间将适合的资源分配给任务并使其生效</li>
<li>相同任务聚合至相同的执行器统一处理</li>
<li>动态调配追加资源至新分配的任务</li>
</ul>
</li>
<li><p>作业治理</p>
</li>
<li><ul>
<li>失效转移</li>
<li>错过作业重新执行</li>
<li>自诊断修复</li>
</ul>
</li>
<li><p>作业依赖(TODO)</p>
</li>
<li><ul>
<li>基于有向无环图（DAG）的作业间依赖</li>
<li>基于有向无环图（DAG）的作业分片间依赖</li>
</ul>
</li>
<li><p>作业开放生态</p>
</li>
<li><ul>
<li>可扩展的作业类型统一接口</li>
<li>丰富的作业类型库，如数据流、脚本、HTTP、文件、大数据等</li>
<li>易于对接业务作业，能够与 Spring 依赖注入无缝整合</li>
</ul>
</li>
<li><p>可视化管控端</p>
</li>
<li><ul>
<li>作业管控端</li>
<li>作业执行历史数据追踪</li>
<li>注册中心管理</li>
</ul>
</li>
</ul>
<h2 id="项目架构"><a href="#项目架构" class="headerlink" title="项目架构"></a>项目架构</h2><h3 id="ElasticJob-lite"><a href="#ElasticJob-lite" class="headerlink" title="ElasticJob-lite"></a>ElasticJob-lite</h3><p>架构图</p>
<p><img src="https://tu.dxysun.com/20201213000839.png" alt=""></p>
<h3 id="ElasticJob-Cloud"><a href="#ElasticJob-Cloud" class="headerlink" title="ElasticJob-Cloud"></a>ElasticJob-Cloud</h3><p>采用自研 Mesos Framework 的解决方案，额外提供资源治理、应用分发以及进程隔离等功能。</p>
<p>架构图 </p>
<p><img src="https://tu.dxysun.com/20201213001106.png" alt=""></p>
<h2 id="调度模型"><a href="#调度模型" class="headerlink" title="调度模型"></a>调度模型</h2><h3 id="进程内调度"><a href="#进程内调度" class="headerlink" title="进程内调度"></a>进程内调度</h3><p>ElasticJob-Lite 是面向进程内的线程级调度框架。通过它，作业能够透明化的与业务应用系统相结合。 它能够方便的与 Spring 、Dubbo 等 Java 框架配合使用，在作业中可自由使用 Spring 注入的 Bean，如数据源连接池、Dubbo 远程服务等，更加方便的贴合业务开发。</p>
<h3 id="进程级调度"><a href="#进程级调度" class="headerlink" title="进程级调度"></a>进程级调度</h3><p>ElasticJob-Cloud 拥有进程内调度和进程级别调度两种方式。 由于 ElasticJob-Cloud 能够对作业服务器的资源进行控制，因此其作业类型可划分为常驻任务和瞬时任务。 常驻任务类似于 ElasticJob-Lite，是进程内调度；瞬时任务则完全不同，它充分的利用了资源分配的削峰填谷能力，是进程级的调度，每次任务的会启动全新的进程处理。</p>
<h2 id="作业接口"><a href="#作业接口" class="headerlink" title="作业接口"></a>作业接口</h2><p>ElasticJob 的作业可划分为基于 class 类型和基于 type 类型两种。</p>
<p>Class 类型的作业由开发者直接使用，需要由开发者实现该作业接口实现业务逻辑。典型代表：Simple 类型、Dataflow 类型。</p>
<ul>
<li><p>Simple 类型，意为简单实现，未经任何封装的类型。需实现 SimpleJob 接口。 该接口仅提供单一方法用于覆盖，此方法将定时执行。 与Quartz原生接口相似，但提供了弹性扩缩容和分片等功能。</p>
</li>
<li><p>Dataflow 类型用于处理数据流，必须实现 fetchData()和 processData()的方法，一个用来获取数据，一个用来处理获取到的数据。</p>
</li>
</ul>
<p>Type 类型的作业只需提供类型名称即可，开发者无需实现该作业接口，而是通过外置配置的方式使用。典型代表：Script 类型、HTTP 类型。</p>
<ul>
<li><p>Script 类型，支持 shell，python，perl 等所有类型脚本。 可通过属性配置 script.command.line 配置待执行脚本，无需编码。 执行脚本路径可包含参数，参数传递完毕后，作业框架会自动追加最后一个参数为作业运行时信息。</p>
</li>
<li><p>HTTP 类型，3.0.0-beta 提供，可通过属性配置http.url,http.method,http.data等配置待请求的http信息。 分片信息以Header形式传递，key为shardingContext，值为json格式。</p>
</li>
</ul>
<h2 id="作业监听"><a href="#作业监听" class="headerlink" title="作业监听"></a>作业监听</h2><h3 id="常规监听器"><a href="#常规监听器" class="headerlink" title="常规监听器"></a>常规监听器</h3><p>例如执行任务处理作业服务器的文件，处理完成后删除文件，可考虑使用每个节点均执行清理任务。 此类型任务实现简单，且无需考虑全局分布式任务是否完成，应尽量使用此类型监听器。</p>
<p>需要实现ElasticJobListener 接口</p>
<h3 id="分布式监听器"><a href="#分布式监听器" class="headerlink" title="分布式监听器"></a>分布式监听器</h3><p>例如作业处理数据库数据，处理完成后只需一个节点完成数据清理任务即可。 此类型任务处理复杂，需同步分布式环境下作业的状态同步，提供了超时设置来避免作业不同步导致的死锁，应谨慎使用。</p>
<p>需要继承AbstractDistributeOnceElasticJobListener 抽象类</p>
<h3 id="SPI实现"><a href="#SPI实现" class="headerlink" title="SPI实现"></a>SPI实现</h3><p>将JobListener实现添加至infra-common下resources/META-INF/services/org.apache.shardingsphere.elasticjob.infra.listener.ElasticJobListener</p>
<h2 id="事件追踪"><a href="#事件追踪" class="headerlink" title="事件追踪"></a>事件追踪</h2><p>ElasticJob 提供了事件追踪功能，可通过事件订阅的方式处理调度过程的重要事件，用于查询、统计和监控。 目前提供了基于关系型数据库的事件订阅方式记录事件。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 初始化数据源</span></span><br><span class="line">DataSource dataSource = ...;</span><br><span class="line"><span class="comment">// 定义日志数据库事件溯源配置</span></span><br><span class="line">TracingConfiguration tracingConfig = <span class="keyword">new</span> TracingConfiguration&lt;&gt;(<span class="string">"RDB"</span>, dataSource);</span><br><span class="line"><span class="comment">// 初始化注册中心</span></span><br><span class="line">CoordinatorRegistryCenter regCenter = ...;</span><br><span class="line"><span class="comment">// 初始化作业配置</span></span><br><span class="line">JobConfiguration jobConfig = ...;</span><br><span class="line">jobConfig.getExtraConfigurations().add(tracingConfig);</span><br><span class="line"><span class="keyword">new</span> ScheduleJobBootstrap(regCenter, jobConfig).schedule();</span><br></pre></td></tr></table></figure>

<p>对应库自动创建 JOB_EXECUTION_LOG 和 JOB_STATUS_TRACE_LOG 两张表以及若干索引。</p>
<h2 id="弹性调度"><a href="#弹性调度" class="headerlink" title="弹性调度"></a>弹性调度</h2><p>弹性调度是 ElasticJob 最重要的功能，能够让任务通过分片进行水平扩展的任务处理。</p>
<h3 id="分片"><a href="#分片" class="headerlink" title="分片"></a>分片</h3><p>ElasticJob 中任务分片项的概念，使得任务可以在分布式的环境下运行，每台任务服务器只运行分配给该服务器的分片。 随着服务器的增加或宕机，ElasticJob 会近乎实时的感知服务器数量的变更，从而重新为分布式的任务服务器分配更加合理的任务分片项，使得任务可以随着资源的增加而提升效率。</p>
<p>任务的分布式执行，需要将一个任务拆分为多个独立的任务项，然后由分布式的服务器分别执行某一个或几个分片项。</p>
<p>举例说明，如果作业分为 4 片，用两台服务器执行，则每个服务器分到 2 片，分别负责作业的 50% 的负载，如下图所示。</p>
<p><img src="https://tu.dxysun.com/20201213001745.png" alt=""></p>
<p>ElasticJob 可以设置分片项和自定义分片参数</p>
<p>个性化参数可以和分片项匹配对应关系，用于将分片项的数字转换为更加可读的业务代码。</p>
<p>例如：按照地区水平拆分数据库，数据库 A 是北京的数据；数据库 B 是上海的数据；数据库 C 是广州的数据。 如果仅按照分片项配置，开发者需要了解 0 表示北京；1 表示上海；2 表示广州。 合理使用个性化参数可以让代码更可读，如果配置为 0=北京,1=上海,2=广州，那么代码中直接使用北京，上海，广州的枚举值即可完成分片项和业务逻辑的对应关系。</p>
<h3 id="资源最大限度利用"><a href="#资源最大限度利用" class="headerlink" title="资源最大限度利用"></a>资源最大限度利用</h3><p>ElasticJob 提供最灵活的方式，最大限度的提高执行作业的吞吐量。 当新增加作业服务器时，ElasticJob 会通过注册中心的临时节点的变化感知到新服务器的存在，并在下次任务调度的时候重新分片，新的服务器会承载一部分作业分片，如下图所示。</p>
<p><img src="https://tu.dxysun.com/20201213001944.png" alt=""></p>
<p>将分片项设置为大于服务器的数量，最好是大于服务器倍数的数量，作业将会合理的利用分布式资源，动态的分配分片项。</p>
<p>例如：3 台服务器，分成 10 片，则分片项分配结果为服务器 A = 0,1,2；服务器 B = 3,4,5；服务器 C = 6,7,8,9。 如果服务器 C 崩溃，则分片项分配结果为服务器 A = 0,1,2,3,4; 服务器 B = 5,6,7,8,9。 在不丢失分片项的情况下，最大限度的利用现有资源提高吞吐量。</p>
<h3 id="高可用"><a href="#高可用" class="headerlink" title="高可用"></a>高可用</h3><p>当作业服务器在运行中宕机时，注册中心同样会通过临时节点感知，并将在下次运行时将分片转移至仍存活的服务器，以达到作业高可用的效果。 本次由于服务器宕机而未执行完的作业，则可以通过失效转移的方式继续执行。如下图所示。</p>
<p><img src="https://tu.dxysun.com/20201213002218.png" alt=""></p>
<h2 id="ElasticJob-Lite-实现原理"><a href="#ElasticJob-Lite-实现原理" class="headerlink" title="ElasticJob-Lite 实现原理"></a>ElasticJob-Lite 实现原理</h2><p>ElasticJob-Lite 并无作业调度中心节点，而是基于部署作业框架的程序在到达相应时间点时各自触发调度。 注册中心仅用于作业注册和监控信息存储。而主作业节点仅用于处理分片和清理等功能。</p>
<h3 id="弹性分布式实现"><a href="#弹性分布式实现" class="headerlink" title="弹性分布式实现"></a>弹性分布式实现</h3><ul>
<li>第一台服务器上线触发主服务器选举。主服务器一旦下线，则重新触发选举，选举过程中阻塞，只有主服务器选举完成，才会执行其他任务。</li>
<li>某作业服务器上线时会自动将服务器信息注册到注册中心，下线时会自动更新服务器状态。</li>
<li>主节点选举，服务器上下线，分片总数变更均更新重新分片标记。</li>
<li>定时任务触发时，如需重新分片，则通过主服务器分片，分片过程中阻塞，分片结束后才可执行任务。如分片过程中主服务器下线，则先选举主服务器，再分片。</li>
<li>通过上一项说明可知，为了维持作业运行时的稳定性，运行过程中只会标记分片状态，不会重新分片。分片仅可能发生在下次任务触发前。</li>
<li>每次分片都会按服务器IP排序，保证分片结果不会产生较大波动。</li>
<li>实现失效转移功能，在某台服务器执行完毕后主动抓取未分配的分片，并且在某台服务器下线后主动寻找可用的服务器执行任务。</li>
</ul>
<h2 id="注册中心数据结构"><a href="#注册中心数据结构" class="headerlink" title="注册中心数据结构"></a>注册中心数据结构</h2><p>注册中心在定义的命名空间下，创建作业名称节点，用于区分不同作业，所以作业一旦创建则不能修改作业名称，如果修改名称将视为新的作业。 作业名称节点下又包含5个数据子节点，分别是 config, instances, sharding, servers 和 leader。</p>
<p><img src="https://tu.dxysun.com/image.png" alt="image"></p>
<p>config 节点：作业配置信息，以 YAML 格式存储</p>
<p>instances 节点：作业运行实例信息，子节点是当前作业运行实例的主键。 作业运行实例主键由作业运行服务器的 IP 地址和 PID 构成。 作业运行实例主键均为临时节点，当作业实例上线时注册，下线时自动清理。注册中心监控这些节点的变化来协调分布式作业的分片以及高可用。 可在作业运行实例节点写入 TRIGGER 表示该实例立即执行一次。</p>
<p>sharding 节点：作业分片信息，子节点是分片项序号，从零开始，至分片总数减一。 分片项序号的子节点存储详细信息。每个分片项下的子节点用于控制和记录分片运行状态。 节点详细信息说明：</p>
<table>
<thead>
<tr>
<th align="left">子节点名</th>
<th>临时节点</th>
<th>描述</th>
</tr>
</thead>
<tbody><tr>
<td align="left">instance</td>
<td>否</td>
<td>执行该分片项的作业运行实例主键</td>
</tr>
<tr>
<td align="left">running</td>
<td>是</td>
<td>分片项正在运行的状态仅配置 monitorExecution 时有效</td>
</tr>
<tr>
<td align="left">failover</td>
<td>是</td>
<td>如果该分片项被失效转移分配给其他作业服务器，则此节点值记录执行此分片的作业服务器 IP</td>
</tr>
<tr>
<td align="left">misfire</td>
<td>否</td>
<td>是否开启错过任务重新执行</td>
</tr>
<tr>
<td align="left">disabled</td>
<td>否</td>
<td>是否禁用此分片项</td>
</tr>
</tbody></table>
<p>servers 节点：作业服务器信息，子节点是作业服务器的 IP 地址。 可在 IP 地址节点写入 DISABLED 表示该服务器禁用。 在新的云原生架构下，servers 节点大幅弱化，仅包含控制服务器是否可以禁用这一功能。 为了更加纯粹的实现作业核心，servers 功能未来可能删除，控制服务器是否禁用的能力应该下放至自动化部署系统。</p>
<p>leader 节点：作业服务器主节点信息，分为 election，sharding 和 failover 三个子节点。 分别用于主节点选举，分片和失效转移处理。</p>
<h2 id="作业流程图"><a href="#作业流程图" class="headerlink" title="作业流程图"></a>作业流程图</h2><h3 id="作业启动"><a href="#作业启动" class="headerlink" title="作业启动"></a>作业启动</h3><p><img src="https://tu.dxysun.com/20201213002700.png" alt="1"></p>
<h3 id="作业执行"><a href="#作业执行" class="headerlink" title="作业执行"></a>作业执行</h3><p><img src="https://tu.dxysun.com/job_exec.jpg" alt="作业执行"></p>
<h2 id="失效转移"><a href="#失效转移" class="headerlink" title="失效转移"></a>失效转移</h2><p>ElasticJob 不会在本次执行过程中进行重新分片，而是等待下次调度之前才开启重新分片流程。 当作业执行过程中服务器宕机，失效转移允许将该次未完成的任务在另一作业节点上补偿执行。</p>
<p>失效转移需要与监听作业运行时状态同时开启才可生效。</p>
<p>失效转移是当前执行作业的临时补偿执行机制，在下次作业运行时，会通过重分片对当前作业分配进行调整。 举例说明，若作业以每小时为间隔执行，每次执行耗时 30 分钟。如下如图所示。</p>
<p><img src="https://tu.dxysun.com/job.png" alt=""></p>
<p>图中表示作业分别于 12:00，13:00 和 14:00 执行。图中显示的当前时间点为 13:00 的作业执行中。</p>
<p>如果作业的其中一个分片服务器在 13:10 的时候宕机，那么剩余的 20 分钟应该处理的业务未得到执行，并且需要在 14:00 时才能再次开始执行下一次作业。 也就是说，在不开启失效转移的情况下，位于该分片的作业有 50 分钟空档期。如下如图所示。</p>
<p><img src="https://tu.dxysun.com/20201213003212.png" alt=""></p>
<p>在开启失效转移功能之后，ElasticJob 的其他服务器能够在感知到宕机的作业服务器之后，补偿执行该分片作业。如下图所示。</p>
<p><img src="https://tu.dxysun.com/20201213003259.png" alt=""></p>
<p>在资源充足的情况下，作业仍然能够在 13:30 完成执行。</p>
<h2 id="执行机制"><a href="#执行机制" class="headerlink" title="执行机制"></a>执行机制</h2><p>当作业执行节点宕机时，会触发失效转移流程。ElasticJob 根据触发时的分布式作业执行的不同状况来决定失效转移的执行时机。</p>
<h3 id="通知执行"><a href="#通知执行" class="headerlink" title="通知执行"></a>通知执行</h3><p>当其他服务器感知到有失效转移的作业需要处理时，且该作业服务器已经完成了本次任务，则会实时的拉取待失效转移的分片项，并开始补偿执行。 也称为实时执行。</p>
<h3 id="问询执行"><a href="#问询执行" class="headerlink" title="问询执行"></a>问询执行</h3><p>作业服务在本次任务执行结束后，会向注册中心问询待执行的失效转移分片项，如果有，则开始补偿执行。 也称为异步执行。</p>
<h3 id="适用场景"><a href="#适用场景" class="headerlink" title="适用场景"></a>适用场景</h3><p>开启失效转移功能，ElasticJob 会监控作业每一分片的执行状态，并将其写入注册中心，供其他节点感知。</p>
<p>在一次运行耗时较长且间隔较长的作业场景，失效转移是提升作业运行实时性的有效手段； 对于间隔较短的作业，会产生大量与注册中心的网络通信，对集群的性能产生影响。 而且间隔较短的作业并未见得关注单次作业的实时性，可以通过下次作业执行的重分片使所有的分片正确执行，因此不建议短间隔作业开启失效转移。</p>
<p>另外需要注意的是，作业本身的幂等性，是保证失效转移正确性的前提。</p>
<h2 id="错过任务重执行"><a href="#错过任务重执行" class="headerlink" title="错过任务重执行"></a>错过任务重执行</h2><h3 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h3><p>ElasticJob 不允许作业在同一时间内叠加执行。 当作业的执行时长超过其运行间隔，错过任务重执行能够保证作业在完成上次的任务后继续执行逾期的作业。</p>
<p>错过任务重执行功能可以使逾期未执行的作业在之前作业执行完成之后立即执行。 举例说明，若作业以每小时为间隔执行，每次执行耗时 30 分钟。如下如图所示。</p>
<p><img src="https://tu.dxysun.com/20201213003547.png" alt=""></p>
<p>图中表示作业分别于 12:00，13:00 和 14:00 执行。图中显示的当前时间点为 13:00 的作业执行中。</p>
<p>如果 12：00 开始执行的作业在 13:10 才执行完毕，那么本该由 13:00 触发的作业则错过了触发时间，需要等待至 14:00 的下次作业触发。 如下如图所示。</p>
<p><img src="https://tu.dxysun.com/20201213003640.png" alt=""></p>
<p>在开启错过任务重执行功能之后，ElasticJob 将会在上次作业执行完毕后，立刻触发执行错过的作业。如下图所示。</p>
<p><img src="https://tu.dxysun.com/20201213003718.png" alt=""></p>
<p>在 13：00 和 14:00 之间错过的作业将会重新执行。</p>
<h3 id="适用场景-1"><a href="#适用场景-1" class="headerlink" title="适用场景"></a>适用场景</h3><p>在一次运行耗时较长且间隔较长的作业场景，错过任务重执行是提升作业运行实时性的有效手段； 对于未见得关注单次作业的实时性的短间隔的作业来说，开启错过任务重执行并无必要。</p>
<h2 id="内置策略"><a href="#内置策略" class="headerlink" title="内置策略"></a>内置策略</h2><h3 id="分片策略"><a href="#分片策略" class="headerlink" title="分片策略"></a>分片策略</h3><p>平均分片策略，根据分片项平均分片</p>
<p>奇偶分片策略，根据作业名称哈希值的奇偶数决定按照作业服务器 IP 升序或是降序的方式分片</p>
<p>轮询分片策略，根据作业名称轮询分片</p>
<h3 id="线程池策略"><a href="#线程池策略" class="headerlink" title="线程池策略"></a>线程池策略</h3><h4 id="CPU-资源策略"><a href="#CPU-资源策略" class="headerlink" title="CPU 资源策略"></a>CPU 资源策略</h4><p>类型：CPU</p>
<p>根据 CPU 核数 * 2 创建作业处理线程池。</p>
<h4 id="单线程策略"><a href="#单线程策略" class="headerlink" title="单线程策略"></a>单线程策略</h4><p>类型：SINGLE_THREAD</p>
<p>使用单线程处理作业。</p>
<h3 id="错误处理策略"><a href="#错误处理策略" class="headerlink" title="错误处理策略"></a>错误处理策略</h3><h4 id="记录日志策略"><a href="#记录日志策略" class="headerlink" title="记录日志策略"></a>记录日志策略</h4><p>类型：LOG</p>
<p>默认内置：是</p>
<p>记录作业异常日志，但不中断作业执行。</p>
<h4 id="抛出异常策略"><a href="#抛出异常策略" class="headerlink" title="抛出异常策略"></a>抛出异常策略</h4><p>类型：THROW</p>
<p>默认内置：是</p>
<p>抛出系统异常并中断作业执行。</p>
<h4 id="忽略异常策略"><a href="#忽略异常策略" class="headerlink" title="忽略异常策略"></a>忽略异常策略</h4><p>类型：IGNORE</p>
<p>默认内置：是</p>
<p>忽略系统异常且不中断作业执行。</p>
<h4 id="邮件通知策略"><a href="#邮件通知策略" class="headerlink" title="邮件通知策略"></a>邮件通知策略</h4><p>类型：EMAIL</p>
<p>默认内置：否</p>
<p>发送邮件消息通知，但不中断作业执行。需添加maven依赖</p>
<h4 id="企业微信通知策略"><a href="#企业微信通知策略" class="headerlink" title="企业微信通知策略"></a>企业微信通知策略</h4><p>类型：WECHAT</p>
<p>默认内置：否</p>
<p>发送企业微信消息通知，但不中断作业执行。需添加maven依赖</p>
<h4 id="钉钉通知策略"><a href="#钉钉通知策略" class="headerlink" title="钉钉通知策略"></a>钉钉通知策略</h4><p>类型：DINGTALK</p>
<p>默认内置：否</p>
<p>发送钉钉消息通知，但不中断作业执行。</p>
<h2 id="运维平台"><a href="#运维平台" class="headerlink" title="运维平台"></a>运维平台</h2><p><a href="https://github.com/apache/shardingsphere-elasticjob-ui" target="_blank" rel="noopener">https://github.com/apache/shardingsphere-elasticjob-ui</a></p>
<p><img src="https://tu.dxysun.com/20210203102638-20210203102639.png" alt=""></p>
<h2 id="未来规划"><a href="#未来规划" class="headerlink" title="未来规划"></a>未来规划</h2><h3 id="作业依赖"><a href="#作业依赖" class="headerlink" title="作业依赖"></a>作业依赖</h3><p>支持基于有向无环图（DAG）的作业依赖。依赖包含基于作业整体维度的依赖，以及基于作业分片项的依赖，打造更加灵活的作业治理解决方案。</p>
<h3 id="调度执行分离"><a href="#调度执行分离" class="headerlink" title="调度执行分离"></a>调度执行分离</h3><p>将调度器和执行器完全分离。调度器可以与执行器一起部署，即为 ElasticJob lite 的无中心化轻量级版本；调度器可以与执行器分离部署，即为 ElasticJob cloud 的资源管控的一站式分布式调度系统。</p>
<h3 id="更加易用的云管产品"><a href="#更加易用的云管产品" class="headerlink" title="更加易用的云管产品"></a>更加易用的云管产品</h3><p>将目前仅支持 Mesos 的 ElasticJob cloud 打造为支持 Mesos 和 Kubernetes 的作业云管平台，并提供无 Mesos 和 Kubernetes 也能够独立使用的不包含资源管控的纯作业管控平台。</p>
<h3 id="可插拔生态"><a href="#可插拔生态" class="headerlink" title="可插拔生态"></a>可插拔生态</h3><p>与 Apache ShardingSphere 一脉相承，ElasticJob 也将提供更加可插拔和模块化架构，为开发者提供基础设施。开发者可以方便的基于 ElasticJob 二次开发，添加各种定制化功能，包括但不限于作业类型（如：大数据作业、HTTP 作业等）、注册中心类型（如：Eureka 等）、执行轨迹存储介质（如其他数据库类型）等。</p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="https://shardingsphere.apache.org" target="_blank" rel="noopener">官网</a></p>
 
      <!-- reward -->
      
      <div id="reword-out">
        <div id="reward-btn">
          打赏
        </div>
      </div>
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="//api.qrserver.com/v1/create-qr-code/?size=150x150&data=https://dxysun.com/2020/12/12/elasticJob/" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>  
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/elastic-job/" rel="tag">elastic-job</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/java/" rel="tag">java</a></li></ul>

    </footer>
  </div>

   
  <nav class="article-nav">
    
      <a href="/2020/12/13/springForMvc/" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            Spring Web MVC
          
        </div>
      </a>
    
    
      <a href="/2020/02/29/javaForDubboSpi/" class="article-nav-link">
        <strong class="article-nav-caption">下一篇</strong>
        <div class="article-nav-title">Dubbo学习笔记之SPI机制</div>
      </a>
    
  </nav>

  
   
  
</article>

</section>
      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2015-2024
        <i class="ri-heart-fill heart_icon"></i> dxysun
      </li>
    </ul>
    <ul>
      <li>
        
        
        
        由 <a href="https://hexo.io" target="_blank">Hexo</a> 强力驱动
        <span class="division">|</span>
        主题 - <a href="https://github.com/Shen-Yu/hexo-theme-ayer" target="_blank">Ayer</a>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>访问人数:<span id="busuanzi_value_site_uv"></span></s>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>浏览次数:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
        <li>
          <a href="https://beian.miit.gov.cn" target="_black" rel="nofollow">豫ICP备17012675号-1</a>
        </li>
        
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
      </li>
    </ul>
  </div>
</footer>
      <div class="float_btns">
        <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

      </div>
    </main>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="https://dxysun.com/static/logo.png" alt="迎着朝阳"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/photos">相册</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/friends">友链</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/about">关于我</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="搜索">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <script>
      if (window.matchMedia("(max-width: 768px)").matches) {
        document.querySelector('.content').classList.remove('on');
        document.querySelector('.sidebar').classList.remove('on');
      }
    </script>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    <div class="reward-item">
      <img class="reward-img" src="https://tu.dxysun.com/alipay-20201219151322.jpg">
      <span class="reward-type">支付宝</span>
    </div>
    
    
    <div class="reward-item">
      <img class="reward-img" src="https://tu.dxysun.com/weixin-20201219151346.png">
      <span class="reward-type">微信</span>
    </div>
    
  </div>
</div>
    
<script src="/js/jquery-2.0.3.min.js"></script>


<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->


<script src="/js/tocbot.min.js"></script>

<script>
  tocbot.init({
    tocSelector: '.tocbot',
    contentSelector: '.article-entry',
    headingSelector: 'h1, h2, h3, h4, h5, h6',
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer: 'main',
    positionFixedSelector: '.tocbot',
    positionFixedClass: 'is-position-fixed',
    fixedSidebarOffset: 'auto'
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css">
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->

<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script>

<!-- MathJax -->

<script type="text/x-mathjax-config">
  MathJax.Hub.Config({
      tex2jax: {
          inlineMath: [ ['$','$'], ["\\(","\\)"]  ],
          processEscapes: true,
          skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
      }
  });

  MathJax.Hub.Queue(function() {
      var all = MathJax.Hub.getAllJax(), i;
      for(i=0; i < all.length; i += 1) {
          all[i].SourceElement().parentNode.className += ' has-jax';
      }
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/mathjax@2.7.6/unpacked/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script>
  var ayerConfig = {
    mathjax: true
  }
</script>

<!-- Katex -->

<!-- busuanzi  -->


<script src="/js/busuanzi-2.3.pure.min.js"></script>


<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->


<link rel="stylesheet" href="/css/clipboard.css">

<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>


<!-- CanvasBackground -->


<script src="/js/dz.js"></script>



    
  </div>
</body>

</html>